#pragma once

template<typename T>
class DBCStore
{
	T* m_records;
	ui8* records;
	std::map<ui32, T*> m_recs;
	std::string m_dbcName;
	ui32 m_recordcount;
	ui32 m_nFields;
	ui32 m_recordSize;
	ui32 m_stringSize;
	char* m_stringBlock;
	std::string m_format;
	CRITICAL_SECTION cs;
public:
	~DBCStore()
	{
	}

	bool Load(const char* fname, const char* format, std::string& error)
	{
		InitializeCriticalSection(&cs);
		m_format = format;
		CMpqFile m_file(fname);
		if(!m_file.IsOpened())
		{
			error = "File not found";
			return false;
		}
		if(m_file.GetSize() < 20)
		{
			error = "File has no valid header";
			return false;
		}

		m_dbcName = fname;
		std::string::size_type p = m_dbcName.rfind('\\');
		m_dbcName = m_dbcName.substr(p + 1, m_dbcName.length());

		char header[4];
		m_file.ReadInc(4, header);
		if(header[0] != 'W' || header[1] != 'D' || header[2] != 'B' || header[3] != 'C')
		{
			error = "File signature is not 'WDBC'";
			return false;
		}

		m_file.ReadInc(4, &m_recordcount);
		m_file.ReadInc(4, &m_nFields);
		m_file.ReadInc(4, &m_recordSize);
		m_file.ReadInc(4, &m_stringSize);
		ui32 size = 0;
		for(ui32 i = 0; i < strlen(format); ++i)
		{
			if(format[i] != 'x')
				size += 4;
		}
		if(size != sizeof(T))
			MessageBoxA(0, "Size incorrect!", "", MB_OK);
		if(strlen(format) != m_nFields)
		{
			error = "Number of fields in the file does not correspond with the expected number of fields";
			return false;
		}
		if(m_file.GetSize() < m_recordcount * m_recordSize + m_stringSize + 20)
		{
			error = "Information from the header result in a bigger size then the file size.";
			return false;
		}
		records = (ui8*)malloc(m_recordcount * m_recordSize);
		m_stringBlock = (char*)malloc(m_stringSize);
		m_file.ReadInc(m_recordcount * m_recordSize, records);
		m_file.ReadInc(m_stringSize, m_stringBlock);
		ui32 blockpointer = 0;
		m_records = new T[m_recordcount];
		for(register ui32 a = 0; a < m_recordcount; ++a)
		{
			char* cpos = (char*)(m_records + a);
			for(register ui32 i = 0; i < strlen(format); )
			{
				switch(*(format + i))
				{
				case 'u':
					{
						*(ui32*)cpos = *(ui32*)(records + blockpointer);
						cpos += 4;
						blockpointer += 4;
						++i;
						break;
					}
				case 'l':
					{
						char** newptr = (char**)cpos;
						ui32 val = 0;
						for(register ui32 f = 0; f < 16 && val == 0; ++f)
						{
							++i;
							if(*(format + i) != 'x' && *(format + i) != 'l')
								break;
							val = *(unsigned int*)(records + blockpointer);
							blockpointer += 4;
						}
						if(val && val < m_stringSize)
							*newptr = (m_stringBlock + val);
						else
							*newptr = 0;
						unsigned int i = (unsigned int) newptr;
						i += 4;
						cpos = (char*)i;
						break;
					}
				case 's':
					{
						char** newptr = (char**)cpos;
						ui32 val = *(ui32*)(records + blockpointer);
						blockpointer += 4;
						*newptr = (m_stringBlock + val);
						unsigned int j = (unsigned int) newptr;
						j += 4;
						cpos = (char*)j;
						++i;
						break;
					}
				case 'f':
					{
						*(float*)cpos = *(float*)(records + blockpointer);
						cpos += 4;
						blockpointer += 4;
						++i;
						break;
					}
				case 'i':
					{
						*(int*)cpos = *(int*)(records + blockpointer);
						cpos += 4;
						blockpointer += 4;
						++i;
						break;
					}
				case 'x':
					{
						blockpointer += 4;
						++i;
						break;
					}
				default:
					++i;
					blockpointer += 4;
					break;
				}
			}
		}
		return true;
	}

	void LockDBC()
	{
		EnterCriticalSection(&cs);
	}

	void ReleaseDBC()
	{
		LeaveCriticalSection(&cs);
	}

	T* LookupEntry(ui32 id)
	{
		EnterCriticalSection(&cs);
		for(ui32 i = 0; i < m_recordcount; ++i)
		{
			if(*(ui32*)(m_records + i) == id)
			{
				T* ret = m_records + i;
				LeaveCriticalSection(&cs);
				return ret;
			}
		}
		LeaveCriticalSection(&cs);
		return 0;
	}

	ui32 GetRowCount() { return m_recordcount; }
	T* GetRow(ui32 id)
	{
		T* tmp = NULL;
		if(id >= m_recordcount)
			return NULL;
		tmp = m_records + id;
		return tmp;
	}
	ui32 AddString(std::string szString)
	{
		realloc(m_stringBlock, m_stringSize + szString.length() + 1);
		m_stringBlock[m_stringSize + szString.length()] = 0;
		memcpy(m_stringBlock + m_stringSize, szString.c_str(), szString.length());
		ui32 addPoint = m_stringSize;
		m_stringSize += szString.length() + 1;
		return addPoint;
	}
	void AddRecord(T* record)
	{
		EnterCriticalSection(&cs);
		T* tmp = new T[m_recordcount + 1];
		memcpy(tmp, m_records, m_recordcount * sizeof(T));
		tmp[m_recordcount] = *record;
		delete [] m_records;
		m_records = tmp;
		realloc(records, (++m_recordcount) * m_recordSize);
		for(ui32 i = 0; i < m_nFields; ++i)
		{
			char& f = m_format.at(i);
			switch(f)
			{
			case 'x':
				*(ui32*)(records + (m_recordcount - 1) * m_recordSize + (i * 4)) = 0;
				break;
			default:
				*(ui32*)(records + (m_recordcount - 1) * m_recordSize + (i * 4)) = *(ui32*)(((ui8*)record) + (i * 4));
				break;
			}
		}
		LeaveCriticalSection(&cs);
	}

	void SaveToFile()
	{
		CreateDirectory("Save", 0);
		CreateDirectory("Save\\DBFilesClient", 0);
		char path[MAX_PATH];
		sprintf_s<MAX_PATH>(path, "Save\\DBFilesClient\\%s", m_dbcName.c_str());
		FILE* f = fopen(path, "wb");
		ui32 magic = 'CBDW';
		fwrite(&magic, 4, 1, f);
		fwrite(&m_recordcount, 4, 1, f);
		fwrite(&m_nFields, 4, 1, f);
		fwrite(&m_recordSize, 4, 1, f);
		fwrite(&m_stringSize, 4, 1, f);
		fwrite(records, m_recordSize * m_recordcount, 1, f);
		fwrite(m_stringBlock, m_stringSize, 1, f);
		fclose(f);
	}

	void ReloadEntry(ui32 entry, T* pEntry = 0)
	{
		EnterCriticalSection(&cs);
		if(!pEntry)
		{
			ui32 i = 0;
			bool fFound = false;
			for( ; i < m_recordcount; ++i)
			{
				if(*(ui32*)(m_records + i) == entry)
				{
					fFound = true;
				}
			}
			if(!fFound)
			{
				LeaveCriticalSection(&cs);
				return;
			}

			for(ui32 j = 0; j < m_nFields; ++j)
			{
				char& f = m_format.at(j);
				switch(f)
				{
				case 'x':
					*(ui32*)(records + i * m_recordSize + (j * 4)) = 0;
					break;
				default:
					*(ui32*)(records + i * m_recordSize + (j * 4)) = *(ui32*)(((ui8*)(m_records + i)) + (j * 4));
					break;
				}
			}
		}
		else
		{
			ui32 i = ((ui32)pEntry) - ((ui32)m_records) / sizeof(T);
			for(ui32 j = 0; j < m_nFields; ++j)
			{
				char& f = m_format.at(j);
				switch(f)
				{
				case 'x':
					*(ui32*)(records + i * m_recordSize + (j * 4)) = 0;
					break;
				default:
					*(ui32*)(records + i * m_recordSize + (j * 4)) = *(ui32*)(((ui8*)pEntry) + (j * 4));
					break;
				}
			}
		}

		LeaveCriticalSection(&cs);
	}

	void SetData(ui32 entry, ui32 position, ui32 data)
	{
		EnterCriticalSection(&cs);
		ui32 i = 0;
		bool fFound = false;
		for( ; i < m_recordcount; ++i)
		{
			if(*(ui32*)(m_records + i) == entry)
			{
				fFound = true;
				break;
			}
		}
		if(!fFound)
		{
			LeaveCriticalSection(&cs);
			return;
		}

		*(ui32*)(((ui8*)(m_records + i)) + (position * 4)) = data;
		*(ui32*)(records + i * m_recordSize + (position * 4)) = data;
		LeaveCriticalSection(&cs);
	}

	void GetEntry(ui32 entry, T** ppOut)
	{
		for(ui32 i = 0; i < m_recordcount; ++i)
		{
			if(*(ui32*)(m_records + i) == entry)
			{
				*ppOut = m_records + i;
				return;
			}
		}
		*ppOut = 0;
	}
};

struct AreaTableEntry
{
	ui32 id;
	ui32 map_id;
	ui32 rec_id;
	ui32 explore;
	ui32 flags;
	ui32 soundRef;
	ui32 unk;
	ui32 soundsRef;
	ui32 zoneRef;
	ui32 zoneSec;
	ui32 areaLevel;
	char* name;
	ui32 nneed[8];
};

struct GroundEffectDoodad
{
	ui32 id;
	char* name;
	ui32 flag;
};

struct GroundEffectTexture
{
	ui32 id;
	ui32 refs[4];
	ui32 weights[4];
	ui32 amount;
	ui32 soundRef;
};

struct MapEntry
{
	ui32 id;
	char* mapName;
	ui32 areaType;
	ui32 unk1;
	ui32 isBattleGround;
	char* displayName;
	ui32 refId;
	char* descHorde;
	char* descAlliance;
	ui32 loadScreenId;
	float mapIconScale;
	//char* reqNormal;
	//char* reqHeroic;
	//char* reqUnk;
	ui32 parentArea;
	float coordX;
	float coordY;
	ui32 unk2[4];
};

struct LoadingScreenEntry
{
	ui32 id;
	char* name;
	char* path;
	ui32 unk1;
};

extern DBCStore<AreaTableEntry> dbcArea;
extern DBCStore<GroundEffectDoodad> dbcGroundEffect;
extern DBCStore<GroundEffectTexture> dbcGroundTexture;
extern DBCStore<MapEntry> dbcMap;
extern DBCStore<LoadingScreenEntry> dbcLoadingScreen;